//
// Created by Administrator on 2022/1/14.
// GPU数据
//

#include "glad/glad.h"
#include "GLFW/glfw3.h"
#include "Common/Utils.h"
#include "glm/glm.hpp"
#include "glm/gtc/type_ptr.hpp"

#define numVAOs 1
#define numVBOs 2
#define numObjects 1
#define BigAmount
#undef BigAmount
#define GPUInstancing
#undef GPUInstancing

float cameraX, cameraY, cameraZ;
glm::vec3 cubePos(4.0f, 0.0f, 0.0f);
glm::vec3 pyramidPos(-4.0f, 0.0f, 0.0f);
GLuint renderingProgram;
GLuint vao[numVAOs];
GLuint vbo[numVBOs];

GLint mvLocation, projLocation;
int width, height;
float aspect;
glm::mat4 pMat, vMat, mMat[numObjects], mvMat[numObjects];
float tf;

void SetupVertices();
void Init();
void Update(GLFWwindow* window, double currentTime);
void Render();

int main()
{
    if(!glfwInit())
    {
        exit(EXIT_FAILURE);
    }

    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 5);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

    GLFWwindow* window = glfwCreateWindow(800, 600, "Chapter4", nullptr, nullptr);
    if(!window)
    {
        glfwTerminate();
        exit(EXIT_FAILURE);
    }
    glfwMakeContextCurrent(window);

    if(!gladLoadGLLoader(GLADloadproc(glfwGetProcAddress)))
    {
        glfwDestroyWindow(window);
        glfwTerminate();
        exit(EXIT_FAILURE);
    }

    Init();

    glfwSwapInterval(1);
    while(!glfwWindowShouldClose(window))
    {
        Update(window, glfwGetTime());
        Render();
        glfwSwapBuffers(window);
        glfwPollEvents();
    }

    glfwDestroyWindow(window);
    glfwTerminate();

    exit(EXIT_SUCCESS);
}

void Init()
{
#if defined(GPUInstancing)
    renderingProgram = Utils::CreateShaderProgram("../../Chapter4/vertInstancedShader.glsl",
#else
    renderingProgram = Utils::CreateShaderProgram("../../Chapter4/vertShader.glsl",
#endif
                                                  "../../Chapter4/fragShader.glsl");
    cameraX = 0.0f;
    cameraY = 0.0f;
#ifdef BigAmount
    cameraZ = 420.0f;
#else
    cameraZ = 8.0f;
#endif
    SetupVertices();
}

void Update(GLFWwindow* window, double currentTime)
{
    // 构造透视矩阵
    glfwGetFramebufferSize(window, &width, &height);
    aspect = float(width) / float(height);
    pMat = glm::perspective(1.0472f, aspect, 0.1f, 1000.0f); //1.0472 radians = 60 degrees

    // 构造view矩阵
    vMat = glm::translate(glm::mat4(1.0f), glm::vec3(-cameraX, -cameraY, -cameraZ));

#if defined(GPUInstancing)
    tf = float(currentTime);
#else
    // 构造model矩阵和mv矩阵
    for(int i = 0; i < numObjects; ++i)
    {
        tf = currentTime + i;
#if BigAmount
        float x = 203.0f * 403.0f / 8000.0f;
        float y = 301.0f * 401.1f / 4001.0f;
        float z = 400.0f * 405.0f / 6003.0f;
#else
        float x = 0.3f * 8.0f;
        float y = 0.4f * 8.0f;
        float z = 0.5f * 8.0f;
#endif
        glm::mat4 tMat = glm::translate(glm::mat4(1.0f),
                                        glm::vec3(sin(x * tf),
                                                  cos(y * tf),
                                                  sin(z * tf)));
        glm::mat4 rMat = glm::rotate(glm::mat4(1.0f), 1.75f * tf, glm::vec3(0.0f, 1.0f, 0.0f));
        rMat = glm::rotate(rMat, 1.75f * tf, glm::vec3(1.0f, 0.0f, 0.0f));
        rMat = glm::rotate(rMat, 1.75f * tf, glm::vec3(0.0f, 0.0f, 1.0f));
        mMat[i] = tMat * rMat;
        mvMat[i] = vMat * mMat[i];
    }
#endif
}

void Render()
{
    glClearColor(0.2f, 0.2f, 0.2f, 0.2f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glUseProgram(renderingProgram);

    // 写到循环里，是因为以后如果是多个物体的渲染，这些都需要per-object进行设置（虽然目前都是一样的）

    // 不使用Instancing，N个物体N个draw call
#if !defined(GPUInstancing)
    for(int i = 0; i < numObjects; ++i)
    {
        // uniform
        mvLocation = glGetUniformLocation(renderingProgram, "mvMatrix");
        projLocation = glGetUniformLocation(renderingProgram, "projMatrix");
        glUniformMatrix4fv(projLocation, 1, GL_FALSE, glm::value_ptr(pMat));

        // draw cube
        // uniform
        mvMat[i] = glm::translate(mvMat[i], cubePos);
        glUniformMatrix4fv(mvLocation, 1, GL_FALSE, glm::value_ptr(mvMat[i]));

        // vertex attributes
        glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, nullptr);
        glEnableVertexAttribArray(0);

        // 开启深度测试，保证后面的面不会在前面绘制。
        glEnable(GL_DEPTH_TEST);
        glDepthFunc(GL_LEQUAL);
        glDrawArrays(GL_TRIANGLES, 0, 36);

        // draw pyramid
        // uniform
        mvMat[i] = glm::translate(mvMat[i], pyramidPos);
        glUniformMatrix4fv(mvLocation, 1, GL_FALSE, glm::value_ptr(mvMat[i]));

        // vertex attributes
        glBindBuffer(GL_ARRAY_BUFFER, vbo[1]);
        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, nullptr);
        glEnableVertexAttribArray(0);

        // 开启深度测试，保证后面的面不会在前面绘制。
        glEnable(GL_DEPTH_TEST);
        glDepthFunc(GL_LEQUAL);
        glDrawArrays(GL_TRIANGLES, 0, 18);
    }
#else
    // uniform
    GLint viewLocation = glGetUniformLocation(renderingProgram, "viewMatrix");
    projLocation = glGetUniformLocation(renderingProgram, "projMatrix");
    GLint tfLocation = glGetUniformLocation(renderingProgram, "tf");
    glUniformMatrix4fv(viewLocation, 1, GL_FALSE, glm::value_ptr(vMat));
    glUniformMatrix4fv(projLocation, 1, GL_FALSE, glm::value_ptr(pMat));
    glUniform1f(tfLocation, tf);

    // vertex attributes
    glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, nullptr);
    glEnableVertexAttribArray(0);

    // 开启深度测试，保证后面的面不会在前面绘制。
    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LEQUAL);
    glDrawArraysInstanced(GL_TRIANGLES, 0, 36, numObjects);
#endif

    // 使用Instancing
}

void SetupVertices()
{
    float cubePositions[108] = {
            -1.0f, 1.0f, -1.0f,
            -1.0f, -1.0f, -1.0f,
            1.0f, -1.0f, -1.0f,
            1.0f, -1.0f, -1.0f,
            1.0f, 1.0f, -1.0f,
            -1.0f, 1.0f, -1.0f,

            1.0f, -1.0f, -1.0f,
            1.0f, -1.0f, 1.0f,
            1.0f, 1.0f, -1.0f,
            1.0f, -1.0f, 1.0f,
            1.0f, 1.0f, 1.0f,
            1.0f, 1.0f, -1.0f,

            1.0f, -1.0f, 1.0f,
            -1.0f, -1.0f, 1.0f,
            1.0f, 1.0f, 1.0f,
            -1.0f, -1.0f, 1.0f,
            -1.0f, 1.0f, 1.0f,
            1.0f, 1.0f, 1.0f,

            -1.0f, -1.0f, 1.0f,
            -1.0f, -1.0f, -1.0f,
            -1.0f, 1.0f, 1.0f,
            -1.0f, -1.0f, -1.0f,
            -1.0f, 1.0f, -1.0f,
            -1.0f, 1.0f, 1.0f,

            -1.0f, -1.0f, 1.0f,
            1.0f, -1.0f, 1.0f,
            1.0f, -1.0f, -1.0f,
            1.0f, -1.0f, -1.0f,
            -1.0f, -1.0f, -1.0f,
            -1.0f, -1.0f, 1.0f,

            -1.0f, 1.0f, -1.0f,
            1.0f, 1.0f, -1.0f,
            1.0f, 1.0f, 1.0f,
            1.0f, 1.0f, 1.0f,
            -1.0f, 1.0f, 1.0f,
            -1.0f, 1.0f, -1.0f
    };

    float pyramidPositions[54] = {
            -1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f, 0.0f, 1.0f, 0.0f, // front face
            1.0f, -1.0f, 1.0f, 1.0f, -1.0f, -1.0f, 0.0f, 1.0f, 0.0f, // right face
            1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, 0.0f, 1.0f, 0.0f, // back face
            -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, 1.0f, 0.0f, 1.0f, 0.0f, // left face
            -1.0f, -1.0f, -1.0f, 1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, // base – left front
            1.0f, -1.0f, 1.0f, -1.0f, -1.0f, -1.0f, 1.0f, -1.0f, -1.0f
    };

    glGenVertexArrays(1, vao);
    glBindVertexArray(vao[0]);
    glGenBuffers(numVBOs, vbo);

    glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
    glBufferData(GL_ARRAY_BUFFER, sizeof(cubePositions), cubePositions, GL_STATIC_DRAW);

    glBindBuffer(GL_ARRAY_BUFFER, vbo[1]);
    glBufferData(GL_ARRAY_BUFFER,sizeof(pyramidPositions), pyramidPositions, GL_STATIC_DRAW);

}